/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */

/* coap-client -- simple CoAP client
 *
 * Copyright (C) 2010--2019 Olaf Bergmann <bergmann@tzi.org> and others
 *
 * This file is part of the CoAP library libcoap. Please see README for terms of
 * use.
 */

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef _WIN32
#define strcasecmp _stricmp
#include "getopt.c"
#if !defined(S_ISDIR)
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif
#else
#include <unistd.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#endif

#include <coap2/coap.h>
#include "coap-client.h"



#define MAX_USER 128 /* Maximum length of a user name (i.e., PSK
                      * identity) in bytes. */
#define MAX_KEY   64 /* Maximum length of a key (i.e., PSK) in bytes. */

#ifdef __GNUC__
#define UNUSED_PARAM __attribute__ ((unused))
#else /* not a GCC */
#define UNUSED_PARAM
#endif /* GCC */

#define FLAGS_BLOCK 0x01

typedef unsigned char method_t;
method_t method = 1;

unsigned int wait_seconds = 90;                /* default timeout in seconds */
unsigned int wait_ms = 0;
int wait_ms_reset = 0;
int obs_started = 0;
unsigned int obs_seconds = 30;          /* default observe time */
unsigned int obs_ms = 0;                /* timeout for current subscription */
int obs_ms_reset = 0;

static int quit = 0;
/* reading is done when this flag is set */
static int ready = 0;
static char* host = NULL;

static unsigned int ping_seconds;

static coap_session_t *session = NULL;
static coap_pdu_t *pdu = NULL;
static coap_context_t *ctx = NULL;

int count = 0;


coap_block_t block = { .num = 0, .m = 0, .szx = 6 };
uint16_t last_block1_tid = 0;
/* optional payload to send */
static coap_string_t payload = { 0, NULL };       

/* Request URI.
 * TODO: associate the resources with transaction id and make it expireable */
static coap_uri_t uri;
static char global_uri[128] = {0};
static coap_optlist_t *optlist = NULL;

static unsigned char _token_data[8];
coap_binary_t the_token = { 0, _token_data };

/* coap server online or offline */
static uint16_t server_online = 0;


uint16_t coap_signal_state() {
  return quit;
}

uint16_t check_server_network_status() {

  /* Create and send ping message */
  if (!session)
    coap_log(LOG_ERR, "session is null \n");

  if (coap_session_send_ping(session) == COAP_INVALID_TID) {
    coap_log(LOG_ERR, "send ping message fail\n");
  } else
    coap_log(LOG_ERR, "send ping message success\n");

  sleep(2); /* sleep 2s */

  uint16_t ret = server_online;
  server_online = 0; /* reset status */

  return ret;

  //return server_online;
}


unsigned short listen_connect_server(const char* host, const char* port) {

 unsigned short listen_ready = 0;

/* frist: judjement server active status */
  if (*check_server_active_status())
    return 0;

  static int count = 0;

  coap_log(LOG_WARNING, "[%d]coap server offine network unreachable\n", count++);
  coap_log(LOG_INFO, "start reconnecting... \n");

  if (init_global_uri(host, "5683", NULL) != 0) {
    coap_log(LOG_ERR, "init global uri failure\n");
    return listen_ready;
  } else
    coap_log(LOG_INFO, "init global uri successful\n");

  if (create_session() != 0) {
    coap_log(LOG_ERR, "create session failure\n");
    return listen_ready;
  } else
    coap_log(LOG_INFO, "create session successful\n");

  if (once_send_message(COAP_REQUEST_GET, 9* 100, NULL, 0) < 0) {
    coap_log(LOG_ERR, "once send coap message failed\n");
    return listen_ready;
  } else
    coap_log(LOG_INFO, "once send coap message successful\n");

  usleep(200 * 1000);

  /* second: judje server active status */
  if (*check_server_active_status()) {
   listen_ready = 1;
  }

  // listen_ready = 1;

  return listen_ready;
}

// uint16_t* listen_server_status() {
//   return &listen_ready;
// }

uint16_t* check_server_active_status() {
  return &server_online ;
}


void
pong_received_handler(coap_context_t *context,
                      coap_session_t *session,
                      coap_pdu_t *received,
                      const coap_tid_t id) {
  coap_log(LOG_INFO, "Received Pong from server\n");
  server_online = 1; // update server status
}

// void 
// pong_received_handler(coap_context_t *context, const coap_address_t *from) {
//   char buff[128] = {0};
//   printf("Received Pong from %s\n", coap_print_addr(from, buff, sizeof(buff)));
//   server_online = 1; // 更新服务器状态为在线
// }

static uint16_t
get_default_port(const coap_uri_t *u) {
  return coap_uri_scheme_is_secure(u) ? COAPS_DEFAULT_PORT : COAP_DEFAULT_PORT;
}


static int
resolve_address(const coap_str_const_t *server, struct sockaddr *dst) {

  struct addrinfo *res, *ainfo;
  struct addrinfo hints;
  static char addrstr[256];
  int error, len=-1;

  memset(addrstr, 0, sizeof(addrstr));
  if (server->length)
    memcpy(addrstr, server->s, server->length);
  else
    memcpy(addrstr, "localhost", 9);

  memset ((char *)&hints, 0, sizeof(hints));
  hints.ai_socktype = SOCK_DGRAM;
  hints.ai_family = AF_UNSPEC;

  error = getaddrinfo(addrstr, NULL, &hints, &res);

  if (error != 0) {
    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(error));
    return error;
  }

  for (ainfo = res; ainfo != NULL; ainfo = ainfo->ai_next) {
    switch (ainfo->ai_family) {
    case AF_INET6:
    case AF_INET:
      len = ainfo->ai_addrlen;
      memcpy(dst, ainfo->ai_addr, len);
      goto finish;
    default:
      ;
    }
  }

 finish:
  freeaddrinfo(res);
  return len;
}



/* SIGINT handler: set quit to 1 for graceful termination */
static void
handle_sigint(int signum UNUSED_PARAM) {
  quit = 1;
}


static inline int
check_token(coap_pdu_t *received) {
  return received->token_length == the_token.length &&
    memcmp(received->token, the_token.s, the_token.length) == 0;
}


static coap_pdu_t *
coap_new_request(coap_context_t *ctx,
                 coap_session_t *session,
                 method_t m,
                 coap_optlist_t **options,
                 unsigned char *data,
                 size_t length) {
  coap_pdu_t *pdu;
  (void)ctx;

  /* usually, requests are sent confirmable */
  unsigned char msgtype = COAP_MESSAGE_CON; 

  if (!(pdu = coap_new_pdu(session))) {
    coap_log(LOG_ERR, "cannot create pdu\n");
    return NULL;
  }
    
  pdu->type = msgtype;
  pdu->tid = coap_new_message_id(session);
  pdu->code = m; /* 1:get, 3: put */

  if ( !coap_add_token(pdu, the_token.length, the_token.s)) {
    coap_log(LOG_DEBUG, "cannot add token to request\n");
  }

  if (options)
    coap_add_optlist_pdu(pdu, options);

  /* chiose use data block or not */
  if (length) {
    if ((0 & FLAGS_BLOCK) == 0)
      coap_add_data(pdu, length, data);
    else
      coap_add_block(pdu, length, data, block.num, block.szx);
  }

  return pdu;
}


static int
event_handler(coap_context_t *ctx UNUSED_PARAM,
              coap_event_t event,
              struct coap_session_t *session UNUSED_PARAM) {

  switch(event) {
  case COAP_EVENT_DTLS_CLOSED:
  case COAP_EVENT_TCP_CLOSED:
  case COAP_EVENT_SESSION_CLOSED:
    quit = 1;
    break;
  default:
    break;
  }
  return 0;
}

/* respone message handle from coap server */
static void
message_handler(struct coap_context_t *ctx,
                coap_session_t *session,
                coap_pdu_t *sent,
                coap_pdu_t *received,
                const coap_tid_t id UNUSED_PARAM) {
  coap_pdu_t *pdu = NULL;
  coap_opt_t *block_opt;
  coap_opt_iterator_t opt_iter;
  unsigned char buf[4];
  coap_optlist_t *option;
  size_t len;
  unsigned char *databuf;
  coap_tid_t tid;

    /* check if this is a response to our original request */
  if (!check_token(received)) {
    /* drop if this was just some message, or send RST in case of notification */
    if (!sent && (received->type == COAP_MESSAGE_CON ||
                  received->type == COAP_MESSAGE_NON))
      coap_send_rst(session, received);
    return;
  }

    if (received->type == COAP_MESSAGE_RST) {
    coap_log(LOG_INFO, "got RST\n");
    return;
  }
/* output the received data, if any */
  if (COAP_RESPONSE_CLASS(received->code) == 2) {

    /* set obs timer if we have successfully subscribed a resource */
    if (!obs_started && coap_check_option(received, COAP_OPTION_OBSERVE, &opt_iter)) {
      coap_log(LOG_DEBUG,
               "observation relationship established, set timeout to %d\n",
               obs_seconds);
      obs_started = 1;
      obs_ms = obs_seconds * 1000;
      obs_ms_reset = 1;
    }

    /* Got some data from server, check if block option is set. Behavior is undefined if
     * both, Block1 and Block2 are present. */
    block_opt = coap_check_option(received, COAP_OPTION_BLOCK2, &opt_iter);
    if (block_opt) { /* handle Block2 */
      uint16_t blktype = opt_iter.type;

      /* TODO: check if we are looking at the correct block number */
      if (coap_get_data(received, &len, &databuf))
        coap_log(LOG_INFO, "recv:%s\n", databuf);

      if (coap_opt_block_num(block_opt) == 0) {
        /* See if observe is set in first response */
        ready = coap_check_option(received,
                                  COAP_OPTION_OBSERVE, &opt_iter) == NULL;
      }
      if(COAP_OPT_BLOCK_MORE(block_opt)) {
        /* more bit is set */
        coap_log(LOG_DEBUG, "found the M bit, block size is %u, block nr. %u\n",
              COAP_OPT_BLOCK_SZX(block_opt),
              coap_opt_block_num(block_opt));

        /* create pdu with request for next block */
        pdu = coap_new_request(ctx, session, method, NULL, NULL, 0); /* first, create bare PDU w/o any option  */
        if ( pdu ) {
          /* add URI components from optlist */
          for (option = optlist; option; option = option->next ) {
            switch (option->number) {
              case COAP_OPTION_URI_HOST :
              case COAP_OPTION_URI_PORT :
              case COAP_OPTION_URI_PATH :
              case COAP_OPTION_URI_QUERY :
                coap_add_option(pdu, option->number, option->length,
                                option->data);
                break;
              default:
                ;     /* skip other options */
            }
          }

          /* finally add updated block option from response, clear M bit */
          /* blocknr = (blocknr & 0xfffffff7) + 0x10; */
          coap_log(LOG_DEBUG, "query block %d\n",
                   (coap_opt_block_num(block_opt) + 1));
          coap_add_option(pdu,
                          blktype,
                          coap_encode_var_safe(buf, sizeof(buf),
                                 ((coap_opt_block_num(block_opt) + 1) << 4) |
                                  COAP_OPT_BLOCK_SZX(block_opt)), buf);

          tid = coap_send(session, pdu);

          if (tid == COAP_INVALID_TID) {
            coap_log(LOG_DEBUG, "message_handler: error sending new request\n");
          } else {
            wait_ms = wait_seconds * 1000;
            wait_ms_reset = 1;
          }

          return;
        }
      }
      return;
    } else { /* no Block2 option */
                /*
    这段代码处理 CoAP 协议中的 Block1 选项，
    用于分块上传数据（即客户端向服务器发送大数据时使用）。
    它检查接收到的消息是否包含 Block1 选项，并根据需要调整块大小和块号。
    */

      block_opt = coap_check_option(received, COAP_OPTION_BLOCK1, &opt_iter);

      if (block_opt) { /* handle Block1 */
        unsigned int szx = COAP_OPT_BLOCK_SZX(block_opt);
        unsigned int num = coap_opt_block_num(block_opt);
        coap_log(LOG_DEBUG,
                 "found Block1 option, block size is %u, block nr. %u\n",
                 szx, num);
        if (szx != block.szx) {
          unsigned int bytes_sent = ((block.num + 1) << (block.szx + 4));
          if (bytes_sent % (1 << (szx + 4)) == 0) {
            /* Recompute the block number of the previous packet given the new block size */
            num = block.num = (bytes_sent >> (szx + 4)) - 1;
            block.szx = szx;
            coap_log(LOG_DEBUG,
                     "new Block1 size is %u, block number %u completed\n",
                     (1 << (block.szx + 4)), block.num);
          } else {
            coap_log(LOG_DEBUG, "ignoring request to increase Block1 size, "
            "next block is not aligned on requested block size boundary. "
            "(%u x %u mod %u = %u != 0)\n",
                  block.num + 1, (1 << (block.szx + 4)), (1 << (szx + 4)),
                  bytes_sent % (1 << (szx + 4)));
          }
        }

        if (payload.length <= (block.num+1) * (1 << (block.szx + 4))) {
          coap_log(LOG_DEBUG, "upload ready\n");
          ready = 1;
          return;
        }
        if (last_block1_tid == received->tid) {
          /*
           * Duplicate BLOCK1 ACK
           *
           * RFCs not clear here, but on a lossy connection, there could
           * be multiple BLOCK1 ACKs, causing the client to retransmit the
           * same block multiple times.
           *
           * Once a block has been ACKd, there is no need to retransmit it.
           */
          return;
        }
        last_block1_tid = received->tid;

        /* create pdu with request for next block */
        pdu = coap_new_request(ctx, session, method, NULL, NULL, 0); /* first, create bare PDU w/o any option  */
        if (pdu) {

          /* add URI components from optlist */
          for (option = optlist; option; option = option->next ) {
            switch (option->number) {
              case COAP_OPTION_URI_HOST :
              case COAP_OPTION_URI_PORT :
              case COAP_OPTION_URI_PATH :
              case COAP_OPTION_CONTENT_FORMAT :
              case COAP_OPTION_URI_QUERY :
                coap_add_option(pdu, option->number, option->length,
                                option->data);
                break;
              default:
              ;     /* skip other options */
            }
          }

          /* finally add updated block option from response, clear M bit */
          /* blocknr = (blocknr & 0xfffffff7) + 0x10; */
          block.num = num + 1;
          block.m = ((block.num+1) * (1 << (block.szx + 4)) < payload.length);

          coap_log(LOG_DEBUG, "send block %d\n", block.num);
          coap_add_option(pdu,
                          COAP_OPTION_BLOCK1,
                          coap_encode_var_safe(buf, sizeof(buf),
                          (block.num << 4) | (block.m << 3) | block.szx), buf);

          coap_add_block(pdu,
                         payload.length,
                         payload.s,
                         block.num,
                         block.szx);
          if (coap_get_log_level() < LOG_DEBUG)
            coap_show_pdu(LOG_INFO, pdu);

          tid = coap_send(session, pdu);

          if (tid == COAP_INVALID_TID) {
            coap_log(LOG_DEBUG, "message_handler: error sending new request\n");
          } else {
            wait_ms = wait_seconds * 1000;
            wait_ms_reset = 1;
          }

          return;
        }
      } else {
        /* There is no block option set, just read the data and we are done. */
        if (coap_get_data(received, &len, &databuf)) {
          server_online = 1;
          coap_log(LOG_INFO, "\nrecv:%s\n", databuf);
        }
      }
    }
  } else {      /* no 2.05 */

    /* check if an error was signaled and output payload if so */
    if (COAP_RESPONSE_CLASS(received->code) >= 4) {
      fprintf(stderr, "%d.%02d",
              (received->code >> 5), received->code & 0x1F);
      if (coap_get_data(received, &len, &databuf)) {
        fprintf(stderr, " ");
        while(len--)
        fprintf(stderr, "%c", *databuf++);
      }
      fprintf(stderr, "\n");
    }

  }

  /* any pdu that has been created in this function must be sent by now */
  assert(pdu == NULL);

  /* our job is done, we can exit at any time */
  ready = coap_check_option(received, COAP_OPTION_OBSERVE, &opt_iter) == NULL;
}

/*
init global uri
*/
int16_t
init_global_uri(char* host, char* port, char* path) {

#define BUFSIZE 40
  unsigned char _buf[BUFSIZE];
  unsigned char *buf = _buf;
  unsigned char portbuf[2];
  size_t buflen;
  int res;
  char temp_uri[128] = {0};
  coap_uri_t t_uri;

   coap_log_t log_level = LOG_DEBUG;
  // coap_set_log_level(7);

  if (path)
    snprintf(temp_uri, sizeof(temp_uri), "coap://%s:%s/%s", host, port, path);
  else
    snprintf(temp_uri, sizeof(temp_uri), "coap://%s:%s/", host, port);

  memcpy(global_uri, temp_uri, sizeof(temp_uri));

  printf("uri:%s\n", temp_uri);

  if (coap_split_uri(temp_uri, strlen(temp_uri), &t_uri) < 0) {
      coap_log(LOG_ERR, "invalid CoAP URI\n");
      return -1;
    }


    if (t_uri.port != get_default_port(&t_uri)) {
      coap_insert_optlist(&optlist,
                  coap_new_optlist(COAP_OPTION_URI_PORT,
                                   coap_encode_var_safe(portbuf, sizeof(portbuf),
                                                        (t_uri.port & 0xffff)),
                  portbuf));
    }

    if (t_uri.path.length) {
      buflen = BUFSIZE;
      res = coap_split_path(t_uri.path.s, t_uri.path.length, buf, &buflen);

      while (res--) {
        coap_insert_optlist(&optlist,
                    coap_new_optlist(COAP_OPTION_URI_PATH,
                    coap_opt_length(buf),
                    coap_opt_value(buf)));

        buf += coap_opt_size(buf);
      }
    }

    return 0;
}

int16_t create_session() {

  if (coap_split_uri(global_uri, strlen(global_uri), &uri) < 0) {
    coap_log(LOG_ERR, "invalid CoAP URI\n");
    return -1;
  }

  int port = uri.port;
  void *addrptr = NULL;
  coap_address_t dst;
  coap_str_const_t server = uri.host;
 
  printf("host:%s\n", uri.host.s);
    /* resolve destination address where server should be sent */
  int res = resolve_address(&server, &dst.addr.sa);
  if (res < 0) {
      fprintf(stderr, "failed to resolve address\n");
      exit(-1);
  }

  ctx = coap_new_context( NULL );
  if ( !ctx ) {
    coap_log( LOG_EMERG, "cannot create context\n" );
    goto finish;
  }

  /* set keep live time */
   ping_seconds = 2;
  // coap_context_set_keepalive(ctx, ping_seconds);


  dst.size = res;
  dst.addr.sin.sin_port = htons( port );

  /* add Uri-Host if server address differs from uri.host */
  switch (dst.addr.sa.sa_family) {
  case AF_INET:
    addrptr = &dst.addr.sin.sin_addr;
    /* create context for IPv4 */
    break;
  case AF_INET6:
    addrptr = &dst.addr.sin6.sin6_addr;
    break;
  default:
    ;
  }
        /* create client session */
  session = coap_new_client_session( ctx, NULL, &dst, COAP_PROTO_UDP );
  if ( !session ) {
      coap_log( LOG_EMERG, "cannot create client session\n" );
      goto finish;
  }



  /* register response and event handle */
  coap_register_option(ctx, COAP_OPTION_BLOCK2);
  coap_register_response_handler(ctx, message_handler);
  coap_register_event_handler(ctx, event_handler);

    /* register pong handler */
  // coap_register_pong_handler(ctx, pong_received_handler);

  static char addr[INET6_ADDRSTRLEN];

/* check host address if legal or not */
  if (addrptr
      && (inet_ntop(dst.addr.sa.sa_family, addrptr, addr, sizeof(addr)) != 0)
      && (strlen(addr) != uri.host.length
      || memcmp(addr, uri.host.s, uri.host.length) != 0)) {
          coap_insert_optlist(&optlist,
            coap_new_optlist(COAP_OPTION_URI_HOST,
            uri.host.length,
            uri.host.s));
  }

  return 0;

finish:
  coap_delete_optlist(optlist);
  coap_session_release( session );
  coap_free_context( ctx );
  coap_cleanup();

  return -1;
}

/*
@Description:
  send message to server
@parament:
  method: the coap message type choise 
  1: get
  2: post
  3: put

@return:
  the return value if send success will greater than zero 
  or less than zero if send failed
*/
int32_t once_send_message(int m, int timeout_ms, char* data, size_t len) {

  int result = -1;
  method = m;

  if ( !ctx ) {
    coap_log( LOG_EMERG, "sss cannot create context\n" );
    goto finish;
  }

  if ( !session ) {
    coap_log( LOG_EMERG, "session is null\n" );
    goto finish;
  }

/* construct CoAP message */
  coap_pdu_t *pdu = coap_new_request(ctx, session, m, &optlist, data, len);
  if (!pdu) {
    coap_log(LOG_ERR, "create pdu failed\n");                            
    goto finish;
  }

  /* send coap message to server */
  if (coap_send(session, pdu) == COAP_INVALID_TID) {
    coap_log(LOG_ERR, "invaild pdu");
    return -1;
  }
  
  wait_ms = timeout_ms;
  coap_log(LOG_DEBUG, "timeout is set to %u seconds\n", timeout_ms);

  struct sigaction sa;
  memset (&sa, 0, sizeof(sa));
  sigemptyset(&sa.sa_mask);
  sa.sa_handler = handle_sigint;
  sa.sa_flags = 0;
  sigaction (SIGINT, &sa, NULL);
  sigaction (SIGTERM, &sa, NULL);

  while (!quit && !(ready && coap_can_exit(ctx)) ) {
    result = coap_run_once( ctx, wait_ms);
    if (result >= 0) {
      if ( wait_ms > 0 && !wait_ms_reset ) {
        if ( (unsigned)result >= wait_ms ) {
          coap_log(LOG_INFO, "timeout\n");
          break;
        }} else {
          wait_ms -= result;
        }
      }
      wait_ms_reset = 0;
    } 

    return result;

finish:
  coap_delete_optlist(optlist);
  coap_session_release( session );
  coap_free_context( ctx );
  coap_cleanup();

  return result;
}

/* Release that session, when over */
void release_session() {

  if (optlist)
    coap_delete_optlist(optlist);
  if (session)
  coap_session_release( session );

  if (ctx)
  coap_free_context( ctx );

  optlist = NULL;
  session = NULL ;
  ctx = NULL;

  coap_cleanup();
}


// int main ()
// {

//   coap_log_t log_level = LOG_INFO;
//   coap_set_log_level(7);

//   if (init_global_uri("192.168.128.112", "5683", NULL) != 0) {
//     coap_log(LOG_ERR, "init global uri failure\n");
//     return -1;
//   } else
//     coap_log(LOG_INFO, "init global uri successful\n");


//   if (create_session() != 0) {
//     coap_log(LOG_ERR, "create session failure\n");
//     return -1;
//   } else
//     coap_log(LOG_INFO, "create session successful\n");

//   char* data = "coap from jc@";

//   if (once_send_message(COAP_REQUEST_GET, 9* 100, NULL, 0) < 0) {
//     coap_log(LOG_ERR, "once send coap message failed\n");
//     return -1;
//   } else
//     coap_log(LOG_INFO, "once send coap message successful\n");


//     release_session();

//     if (init_global_uri("192.168.128.112", "5683", "data") != 0) {
//     coap_log(LOG_ERR, "init global uri failure\n");
//     return -1;
//   } else
//     coap_log(LOG_INFO, "init global uri successful\n");


//   if (create_session() != 0) {
//     coap_log(LOG_ERR, "create session failure\n");
//     return -1;
//   } else
//     coap_log(LOG_INFO, "create session successful\n");

//     if (once_send_message(COAP_REQUEST_PUT, 9* 100, data, strlen(data)) < 0) {
//       coap_log(LOG_ERR, "once send coap message failed\n");
//       return -1;
//     } else
//       coap_log(LOG_INFO, "once send coap message successful\n");

//       release_session();
// }